<!-- Licensed under a BSD license. See license.html for license -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
    <title>Three.js - Lots of Objects - Merged</title>
    <style>
    html, body {
        height: 100%;
        margin: 0;
    }
    #c {
        width: 100%;
        height: 100%;
        display: block;
    }
    </style>
  </head>
  <body>
    <canvas id="c"></canvas>
  </body>
<script type="importmap">
{
  "imports": {
    "three": "../../build/three.module.js",
    "three/addons/": "../../examples/jsm/"
  }
}
</script>

<script type="module">
import * as THREE from 'three';
import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

function main() {

	const canvas = document.querySelector( '#c' );
	const renderer = new THREE.WebGLRenderer( { antialias: true, canvas } );

	const fov = 60;
	const aspect = 2; // the canvas default
	const near = 0.1;
	const far = 10;
	const camera = new THREE.PerspectiveCamera( fov, aspect, near, far );
	camera.position.z = 2.5;

	const controls = new OrbitControls( camera, canvas );
	controls.enableDamping = true;
	controls.enablePan = false;
	controls.minDistance = 1.2;
	controls.maxDistance = 4;
	controls.update();

	const scene = new THREE.Scene();
	scene.background = new THREE.Color( 'black' );

	{

		const loader = new THREE.TextureLoader();
		const texture = loader.load( 'resources/images/world.jpg', render );
		texture.colorSpace = THREE.SRGBColorSpace;
		const geometry = new THREE.SphereGeometry( 1, 64, 32 );
		const material = new THREE.MeshBasicMaterial( { map: texture } );
		scene.add( new THREE.Mesh( geometry, material ) );

	}

	async function loadFile( url ) {

		const req = await fetch( url );
		return req.text();

	}

	function parseData( text ) {

		const data = [];
		const settings = { data };
		let max;
		let min;
		// split into lines
		text.split( '\n' ).forEach( ( line ) => {

			// split the line by whitespace
			const parts = line.trim().split( /\s+/ );
			if ( parts.length === 2 ) {

				// only 2 parts, must be a key/value pair
				settings[ parts[ 0 ] ] = parseFloat( parts[ 1 ] );

			} else if ( parts.length > 2 ) {

				// more than 2 parts, must be data
				const values = parts.map( ( v ) => {

					const value = parseFloat( v );
					if ( value === settings.NODATA_value ) {

						return undefined;

					}

					max = Math.max( max === undefined ? value : max, value );
					min = Math.min( min === undefined ? value : min, value );
					return value;

				} );
				data.push( values );

			}

		} );
		return Object.assign( settings, { min, max } );

	}

	function addBoxes( file ) {

		const { min, max, data } = file;
		const range = max - min;

		// these helpers will make it easy to position the boxes
		// We can rotate the lon helper on its Y axis to the longitude
		const lonHelper = new THREE.Object3D();
		scene.add( lonHelper );
		// We rotate the latHelper on its X axis to the latitude
		const latHelper = new THREE.Object3D();
		lonHelper.add( latHelper );
		// The position helper moves the object to the edge of the sphere
		const positionHelper = new THREE.Object3D();
		positionHelper.position.z = 1;
		latHelper.add( positionHelper );
		// Used to move the center of the cube so it scales from the position Z axis
		const originHelper = new THREE.Object3D();
		originHelper.position.z = 0.5;
		positionHelper.add( originHelper );

		const lonFudge = Math.PI * .5;
		const latFudge = Math.PI * - 0.135;
		const geometries = [];
		data.forEach( ( row, latNdx ) => {

			row.forEach( ( value, lonNdx ) => {

				if ( value === undefined ) {

					return;

				}

				const amount = ( value - min ) / range;

				const boxWidth = 1;
				const boxHeight = 1;
				const boxDepth = 1;
				const geometry = new THREE.BoxGeometry( boxWidth, boxHeight, boxDepth );

				// adjust the helpers to point to the latitude and longitude
				lonHelper.rotation.y = THREE.MathUtils.degToRad( lonNdx + file.xllcorner ) + lonFudge;
				latHelper.rotation.x = THREE.MathUtils.degToRad( latNdx + file.yllcorner ) + latFudge;

				// use the world matrix of the origin helper to
				// position this geometry
				positionHelper.scale.set( 0.005, 0.005, THREE.MathUtils.lerp( 0.01, 0.5, amount ) );
				originHelper.updateWorldMatrix( true, false );
				geometry.applyMatrix4( originHelper.matrixWorld );

				geometries.push( geometry );

			} );

		} );

		const mergedGeometry = BufferGeometryUtils.mergeGeometries(
			geometries, false );
		const material = new THREE.MeshBasicMaterial( { color: 'red' } );
		const mesh = new THREE.Mesh( mergedGeometry, material );
		scene.add( mesh );

	}

	loadFile( 'resources/data/gpw/gpw_v4_basic_demographic_characteristics_rev10_a000_014mt_2010_cntm_1_deg.asc' )
		.then( parseData )
		.then( addBoxes )
		.then( render );

	function resizeRendererToDisplaySize( renderer ) {

		const canvas = renderer.domElement;
		const width = canvas.clientWidth;
		const height = canvas.clientHeight;
		const needResize = canvas.width !== width || canvas.height !== height;
		if ( needResize ) {

			renderer.setSize( width, height, false );

		}

		return needResize;

	}

	let renderRequested = false;

	function render() {

		renderRequested = undefined;

		if ( resizeRendererToDisplaySize( renderer ) ) {

			const canvas = renderer.domElement;
			camera.aspect = canvas.clientWidth / canvas.clientHeight;
			camera.updateProjectionMatrix();

		}

		controls.update();
		renderer.render( scene, camera );

	}

	render();

	function requestRenderIfNotRequested() {

		if ( ! renderRequested ) {

			renderRequested = true;
			requestAnimationFrame( render );

		}

	}

	controls.addEventListener( 'change', requestRenderIfNotRequested );
	window.addEventListener( 'resize', requestRenderIfNotRequested );

}

main();
</script>
</html>
